package com.iraytek.rtspdemo;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.ImageReader;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.util.Size;

import androidx.annotation.FloatRange;
import androidx.annotation.IntRange;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class CameraUtil {
    private static String TAG = "CameraUtil";
    private static List<CameraStateCallback> callbackList;
    //better be changed in activity's onCreate(), or it may have no effect
    public static SizeRatio sizeRatio = SizeRatio.MIDDLE;

    public static enum SizeRatio {
        /**
         * 红外相机
         */
        WIDE(5, 4),
        MIDDLE(4, 3),
        NARROW(16, 9);
        public int w;
        public int h;

        SizeRatio(int w, int h) {
            this.w = w;
            this.h = h;
        }
    }

    /**
     * get supported format of this camera
     *
     * @param cameraId
     * @return array of supported format,see {@link ImageFormat}
     * @throws CameraAccessException
     */
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public static int[] getSupportFormat(String cameraId) throws CameraAccessException {
        CameraManager cameraManager = getCameraManager();
        CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
        StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
        return map.getOutputFormats();
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private static CameraManager getCameraManager() {
        return (CameraManager) RtspApplication.context.getSystemService(Context.CAMERA_SERVICE);
    }

    /**
     * open camera with ImageReader's callback;
     * don't forget to close camera by invoking {@link #closeCamera(int)} or {@link #closeAllCameras()}
     * if you don't need camera.
     *
     * @param index             the camera index to open
     * @param imageReaderFormat ImageReader's format,see {@link ImageFormat}
     * @param listener          callback when has new image
     * @param textureHolder     used to hold SurfaceTexture that will show preview
     * @param maxWidth          limit the max width of output image,
     *                          if maxWidth is less than all supported sizes, 640x480 will be used.
     * @return image size that ImageReader used, or null if there's exception while open camera
     */
    @SuppressLint("MissingPermission")
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public static synchronized Size openCamera(int index, int imageReaderFormat, ImageReader.OnImageAvailableListener listener, @Nullable SurfaceTextureHolder textureHolder, int maxWidth) {
        if (cameraOpened(index)) {//do not open camera twice
            return null;
        }
        CameraManager cameraManager = getCameraManager();
        try {
            String[] idList = cameraManager.getCameraIdList();

            HandlerThread thread = new HandlerThread(TAG + index);
            thread.start();
            Handler handler = new Handler(thread.getLooper());

            Size size = checkSize(idList[index], imageReaderFormat, maxWidth);
            CameraStateCallback callback = new CameraStateCallback(imageReaderFormat, listener, textureHolder);
            callback.setCameraIndex(index);
            callback.setHandler(handler);
            callback.setSize(size);
            cameraManager.openCamera(idList[index], callback, handler);
            if (callbackList == null) {
                callbackList = new ArrayList<>(2);
            }
            callbackList.add(callback);
            return size;
        } catch (CameraAccessException | ArrayIndexOutOfBoundsException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static Size openCamera(int index, int imageReaderFormat, ImageReader.OnImageAvailableListener listener, @Nullable SurfaceTextureHolder textureHolder) {
        return openCamera(index, imageReaderFormat, listener, textureHolder, 0);
    }

    public static boolean cameraOpened(@IntRange(from = 0) int cameraIndex) {
        if (callbackList == null) {
            return false;
        }
        for (CameraStateCallback callback : callbackList) {
            if (callback.getCameraIndex() == cameraIndex) {
                return true;
            }
        }
        return false;
    }

    /**
     * Close all cameras to release resources
     */
    public static void closeAllCameras() {
        if (callbackList == null) {
            return;
        }
        for (CameraStateCallback callback : callbackList) {
            callback.close();
        }
        callbackList.clear();
        callbackList = null;
    }

    /**
     * Close previously opened camera
     *
     * @param index camera index
     * @return CameraStateCallback that related to the camera, or null if no opened camera found.
     */
    public static CameraStateCallback closeCamera(@IntRange(from = 0) int index) {
        if (callbackList == null) {
            return null;
        }
        Iterator<CameraStateCallback> iterator = callbackList.iterator();
        while (iterator.hasNext()) {
            CameraStateCallback next = iterator.next();
            if (next.getCameraIndex() == index) {
                next.close();
                iterator.remove();
                return next;
            }
        }
        return null;
    }

    /**
     * Take photo, especially for rgb camera, it may cost some time (about 1-5s),
     *
     * @return capture result or null if capture failed.
     */
    public static Bitmap takePhoto(int cameraIndex) {
        if (callbackList == null) {
            return null;
        }
        for (CameraStateCallback callback : callbackList) {
            if (callback.getCameraIndex() == cameraIndex) {
                return callback.takePhoto();
            }
        }
        return null;
    }

    /**
     * switch camera, with same params except camera index;
     * it will reopen camera if {@code originIndex}={@code replaceIndex}
     *
     * @param originIndex  the camera index to close
     * @param replaceIndex the camera index to open
     */
    @Deprecated
    public static void switchCamera(int originIndex, int replaceIndex) {
        CameraStateCallback callback = closeCamera(originIndex);
        if (callback != null) {
            openCamera(replaceIndex, callback.getFormat(), callback.getOnImageAvailableListener(), null, callback.getSize().getWidth());
        }
    }

    /**
     * 数码变焦
     *
     * @param cameraIndex 相机的索引值
     * @param zoom        缩放，一般最大是8.0
     */
    public static void zoom(int cameraIndex, @FloatRange(from = 1, to = 10) float zoom) {
        for (CameraStateCallback cb : callbackList) {
            if (cb.getCameraIndex() == cameraIndex) {
                cb.setZoom(zoom);
                break;
            }
        }
    }

    public static Size checkSize(String id, int format, int maxWidth) throws CameraAccessException {
        CameraManager manager = getCameraManager();
        CameraCharacteristics characteristics = manager.getCameraCharacteristics(id);
        StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
        Size[] outputSizes = map.getOutputSizes(format);
        Log.d(TAG, "checkSize: " + Arrays.toString(outputSizes));

        if (outputSizes != null && outputSizes.length > 0) {
            for (Size outputSize : outputSizes) {
                if (maxWidth <= 0 || outputSize.getWidth() <= maxWidth) {
                    return outputSize;
                }
            }
            return outputSizes[0];
        }
        return new Size(640, 480);
    }

    /**
     * 获取按比例缩放后的rect，从中间截取，用于数码变焦
     *
     * @param cameraId
     * @param zoom     缩放比例
     * @throws CameraAccessException
     */
    public static Rect getScaledRect(String cameraId, float zoom) throws CameraAccessException {
        CameraManager cameraManager = (CameraManager) RtspApplication.context.getSystemService(Context.CAMERA_SERVICE);
        CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId);
        Float maxZoom = cameraCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);//generally 8.0
        if (zoom > maxZoom) {
            zoom = maxZoom;
        } else if (zoom < 1) {
            zoom = 1;
        }
        Rect rect = cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
        int dx = (int) (rect.width() * (1 - 1 / zoom) / 2);
        int dy = (int) (rect.height() * (1 - 1 / zoom) / 2);
        return new Rect(rect.left + dx, rect.top + dy, rect.right - dx, rect.bottom - dy);
    }

    public interface SurfaceTextureHolder {
        SurfaceTexture getSurfaceTexture();
    }

}
